#include "sound.h"
#include "common.h"
#include "fmod.hpp"
#include "fmod_errors.h"
#include <windows.h>
#include <iostream>
#include <conio.h>
#include <iomanip>
#include <cmath>

using namespace std;

// Returns octave from given frequency
int GetNoteOctave(float freq)
{
	if (freq < 31.0f) return 0;
	else if (freq < 62.0f) return 1;
	else if (freq < 124.0f) return 2;
	else if (freq < 248.0f) return 3;
	else if (freq < 495.0f) return 4;
	else if (freq < 990.0f) return 5;
	else if (freq < 2000.0f) return 6;
	else if (freq < 3960.0f) return 7;
	else if (freq < 8000.0f) return 8;
	else if (freq < 16000.0f) return 9;
	else return 10;
}


/*==============================================================================
		Sound class controls everything about the audio input.
==============================================================================*/

Sound::Sound()	
	: system(NULL), sound(NULL), channel(NULL), audio_dev_id(0), record_dev_id(0),
	volume(0.0f), freq(0.0f), bass_freq(0.0f), mid_freq(0.0f), high_freq(0.0f),
	db(0.0f), bass_db(0.0f), mid_db(0.0f), high_db(0.0f), octave(0), spectrum(NULL),
	sound_initialized(false), sound_closed(false)
{
	spectrum = new float[SPECTRUMSIZE];
}

Sound::~Sound()
{
}

/*==============================================================================
		Initialize system, create sound object to put recorded input
==============================================================================*/
void Sound::Init()
{
	LOG_INFO("Initializing sound...");

	// Create system object
	result = FMOD::System_Create(&system);
	ERRCHECK(result);

	// Check FMOD library version
	result = system->getVersion(&version);
	ERRCHECK(result);

	cout << "You are using FMOD ver." << FMOD_VERSION << endl;
	cout << "====================================================" << endl;

	if (version < FMOD_VERSION)
		LOG_ERROR("FMOD error: Your FMOD version is too old.");

	// Select default audio driver
	result = system->setDriver(audio_dev_id);
	ERRCHECK(result);

	// Output default audio driver name
	result = system->getDriverInfo(audio_dev_id, audio_dev_name, 256, 0);
	ERRCHECK(result);
	cout << "Audio device: " << audio_dev_name << endl;
	cout << "====================================================" << endl;

	// Output default record device name
	result = system->getRecordDriverInfo(record_dev_id, record_dev_name, 256, 0);
	ERRCHECK(result);
	cout << "Record device: " << record_dev_name << endl;
	cout << "====================================================" << endl;

	// Initialize system

	// Following line used to be sure that later used sound will be created correctly
	result = system->setSoftwareFormat(OUTPUTRATE, FMOD_SOUND_FORMAT_PCM16, 1, 0, FMOD_DSP_RESAMPLER_LINEAR);
    ERRCHECK(result);

	result = system->init(32, FMOD_INIT_NORMAL, NULL);
	ERRCHECK(result);
	
	memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));

	exinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);
	exinfo.numchannels = 1;
	exinfo.defaultfrequency = OUTPUTRATE;
	exinfo.format = FMOD_SOUND_FORMAT_PCM16;
	exinfo.length = exinfo.defaultfrequency * sizeof(short) * exinfo.numchannels * 5;

	result = system->createSound(0, FMOD_2D | FMOD_SOFTWARE | FMOD_LOOP_NORMAL | FMOD_OPENUSER, &exinfo, &sound);
	ERRCHECK(result);

	sound_initialized = true;
}

/*==============================================================================
						Tells if system is initialized
==============================================================================*/
bool Sound::IsInit()
{
	return sound_initialized;
}

/*==============================================================================
							Starts recording
==============================================================================*/
void Sound::Run()
{
	cout << "Starting recording..." << endl;

	result = system->recordStart(record_dev_id, sound, true);
	ERRCHECK(result);

	// Wait a little to record some sound
	Sleep(100);

	result = system->playSound(FMOD_CHANNEL_REUSE, sound, false, &channel);
	ERRCHECK(result);
	
	// If volume won't be 0, then it will be feedback
	result = channel->setVolume(0.0f);
	ERRCHECK(result);
}

/*==============================================================================
						To get frequency in hertz
==============================================================================*/
float Sound::GetFreq(std::string range)
{
	if (range == "BASS") return bass_freq;
	if (range == "MID")	return mid_freq;
	if (range == "HIGH") return high_freq;
	return freq;
}

/*==============================================================================
						To get intensity in decibels
==============================================================================*/
float Sound::GetDB(std::string range)
{
	if (range == "BASS") return bass_db;
	if (range == "MID") return mid_db;
	if (range == "HIGH") return high_db;
	return db;
}

/*==============================================================================
							To get octave number
==============================================================================*/
int Sound::GetOctave()
{
	return octave;
}

/*==============================================================================
							To get entry frequency
==============================================================================*/
float Sound::GetEntryHz()
{
	return (float)OUTPUTRATE / 2.0f / (float)SPECTRUMSIZE;
}

/*==============================================================================
					Returns sound spectrum and it's size
==============================================================================*/
float* Sound::GetSpectrum()
{
	return spectrum;
}

/*==============================================================================
					Returns note's name depending on frequency
==============================================================================*/
std::string Sound::GetNote()
{
	int note_id = 0;
	
	for (int i = 0; i < 120; i++)
	{
		if (freq >= notefreq[i] && freq < notefreq[i+1])
		{
			if (fabs(freq - notefreq[i]) < fabs(freq - notefreq[i+1]))
				note_id = i;
			else note_id = i + 1;
			break;
		}
	}

	return note[note_id];
}


/*==============================================================================
							Tests sound recording
==============================================================================*/
void Sound::SoundCheck()
{
	Init();
	Run();

	int key = 0; // detects which keyboard button is pressed

	do 
	{
		Update();

		cout << "freq: " << freq << "hz, db: " << db << '\r';
			
		// Take pressed key number
		if (_kbhit()) key = _getch();
	} while (key != 27);

	Close();
}

/*==============================================================================
		Analyzes recorded sound spectrum, counts frequency to hertz
==============================================================================*/
void Sound::Update()
{
	float temp_freq = 0.0f;
	int bin = 0;

	int bass_sum = 0, 
		mid_sum = 0,
		high_sum = 0;
	
	int bass_bin = 0,
		mid_bin = 0,
		high_bin = 0;

	db = bass_db = mid_db = high_db = 0.0f;
	freq = bass_freq = mid_freq = high_freq = 0.0f;
	
	// Take input signal, so we could analyze it

	result = channel->getSpectrum(spectrum, SPECTRUMSIZE, 0, FMOD_DSP_FFT_WINDOW_MAX);
	ERRCHECK(result);

	for (int i=0; i<SPECTRUMSIZE; i++)
	{
		temp_freq = float(i) * BINSIZE;

		if (temp_freq >= 50.0f && temp_freq < 140.0f) 
		{
			// this is in "bass" range.
			if (spectrum[i] > 0.01f && spectrum[i] > bass_db)
			{
				bass_db = spectrum[i];
				bass_bin = i;
			}
			bass_sum++;
		}
		else if (temp_freq >= 140.0f && temp_freq < 240.0f)
		{
			// this is in "mid" range.
			if (spectrum[i] > 0.01f && spectrum[i] > mid_db)
			{
				mid_db = spectrum[i];
				mid_bin = i;
			}
			mid_sum++;
		}
		else if (temp_freq >= 240.0f && temp_freq < MAXFREQ)
		{
			// this is "real treble" range for guitar.
			if (spectrum[i] > 0.01f && spectrum[i] > high_db)
			{
				high_db = spectrum[i];
				high_bin = i;
			}
			high_sum++;
		}
		else spectrum[i] = 0.0f;

		// Check which value is max
		if (spectrum[i] > 0.01f && spectrum[i] > db)
		{
			// Remembers loudest frequency 
			db = spectrum[i];
			bin = i;
		}

	}
	
	freq = float(bin) * BINSIZE;
	bass_freq = float(bass_bin) * BINSIZE;
	mid_freq = float(mid_bin) * BINSIZE;
	high_freq = float(high_bin) * BINSIZE;

	octave = GetNoteOctave(freq);
	system->update();
}
/*==============================================================================
				Release all resources, ends sound recording
==============================================================================*/
void Sound::Close()
{
	LOG_INFO("Closing Sound object...");

	cout << endl;
	cout << "Done recording." << endl;
	cout << "====================================================" << endl;

	// Free all resources
	result = sound->release();
	ERRCHECK(result);

	result = system->release();
	ERRCHECK(result);

	sound_closed = true;
}
bool Sound::IsClosed()
{
	return sound_closed;
}

// Function deals with errors
void Sound::ERRCHECK(FMOD_RESULT result)
{
	if (result != FMOD_OK)
	{
		std::string error_msg = " FMOD error! ";
		error_msg += result + ") " + error_msg += (FMOD_ErrorString(result)) + '\n';

		LOG_ERROR(error_msg);
		exit(-1);
	}
}
